package com.example.redis_redisson_demo.web.redisson;

import com.example.redis_redisson_demo.common.utils.R;
import com.example.redis_redisson_demo.dao.AttrDao;
import com.example.redis_redisson_demo.entity.AttrEntity;
import net.sf.jsqlparser.statement.select.KSQLWindow;
import org.redisson.Redisson;
import org.redisson.RedissonMultiLock;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletRequest;
import javax.xml.crypto.Data;
import java.sql.Time;
import java.util.Date;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * redisson分布式锁。
 * 要明白，完全不是为了缓存而生，而是为了解决分布式问题而生。
 * redisson分布式锁均提供异步实现方法，但实际上对于锁而言，很少用到异步。
 * 因为用锁就是为了阻塞后面的任务，你还用异步，异步回来没获得到锁，结果业务代码都执行完了
 * <p>
 * 如果使用StringRedisTemplate来实现分布式锁，存在很多棘手的问题:
 * 对锁的操作是否原子？出现跳锁现象怎么办？具体去看高级篇笔记，这里不再赘述
 * 而使用redisson则直接解决了这些问题
 */
@RestController
@RequestMapping("/redissondistributedlock")
public class RedissonDistributedLockController {
    @Autowired
    RedissonClient redissonClient;
    @Autowired
    AttrDao attrDao;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @RequestMapping("/t1")
    public R t1(ServletRequest servletRequest) {
        System.out.println(Thread.currentThread().getId());
        RLock redisson_lock = redissonClient.getLock("redisson_lock");
        redisson_lock.lock(30, TimeUnit.SECONDS);
        System.out.println("get Locked");
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            System.out.println("unlock");
            redisson_lock.unlock();
        }
        return R.ok();
    }


    /**
     * 可重入锁
     * 不同与StringRedisTemplate为了解决跳锁的问题，每个锁还得附加一个当前线程的UUID
     * redisson的可重入锁天生就解决了跳锁的问题，一个线程只能解开自己线程的锁
     * <p>
     * https://www.javadoc.io/doc/org.redisson/redisson/3.10.0/org/redisson/api/RLock.html
     *
     * @return
     */
    @RequestMapping("/rlock")
    public R redissonRLock() {
        String redissonPrefix = "demo:redisson:disLock:";
        AttrEntity attrEntity = attrDao.selectById(2L);

        /**
         * RLock getLock(key)
         * 获取一个指定key的R锁。只要key相同，那么不管在上海北京还是广东，操作的都是同一个R锁
         */
        RLock rLock = redissonClient.getLock(redissonPrefix + "RLock");

        /**
         * Redisson所有的分布式锁都先继承Lock包下的方法
         * https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/locks/Lock.html
         */

        /**
         * void lock() / void lock(long leaseTime, TimeUnit unit)
         * 上锁 / 设置过期时间的上锁
         * 如果不设置过期时间，则自动用看门狗为锁持续更新过期时间，否则按照指定的过期时间过期
         * 提供异步实现方法
         *
         * lock()方法会一直阻塞等待锁，!!!且在阻塞期间不会响应线程中断命令!!!
         */
        rLock.lock(10, TimeUnit.SECONDS);

        /**
         * void lockInterruptibly() throws InterruptedException /
         * void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException
         * 响应中断式上锁。无异步实现
         *
         * 会一直阻塞等待锁，!!!!但是在阻塞期间可以响应线程中断命令!!!!
         */
        try {
            rLock.lockInterruptibly();
        } catch (InterruptedException e) {
            logger.warn("锁响应中断事件");
            e.printStackTrace();
        }

        /**
         * tryLock() 尝试获取锁，无法获取立即返回
         * tryLock(long waitTime, TimeUnit unit)  尝试在waitTime期间内获取锁，超时后无法获取立即返回
         * tryLock(long waitTime, long leaseTime, TimeUnit unit)  尝试在waitTime时间内获取锁，获得后还要给锁添加过期时间
         * tryLock可以响应中断事件
         * 均提供异步实现
         */
        try {
            rLock.tryLock(5, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            logger.warn("锁响应中断事件");
            e.printStackTrace();
        }

        /**
         * void unlock()
         * 解锁。redisson天生实现对应锁。只能解开自身线程的锁，或自身线程多次重入的锁
         * 如果尝试解开其他人的锁，或在自身锁已经失效的情况下解空锁，都会抛出 IllegalMonitorStateException 异常
         */
        try {
            rLock.unlock();
        } catch (IllegalMonitorStateException e) {
            logger.warn("错误释放非对应的锁");
            e.printStackTrace();
        }

        /**
         * 以下为RLock自身的方法
         */
        /**
         * boolean forceUnlock()
         * 强制解锁。不管这个锁是不是被其他线程持有
         * 提供异步实现
         */
        rLock.forceUnlock();

        /**
         * int getHoldCount()
         * 获取当前线程对该锁的重入次数
         */
        int holdCount = rLock.getHoldCount();

        /**
         * 	boolean isHeldByCurrentThread()
         * 	检查该锁是否正在被当前线程持有(包括重入)
         */
        boolean heldByCurrentThread = rLock.isHeldByCurrentThread();

        /**
         * boolean isHeldByThread(long l)
         * 检查该锁是否被给定线程号的线程持有
         */
        boolean heldByThread = rLock.isHeldByThread(88L);

        /**
         * boolean isLocked()
         * 检查该锁是否正在上锁中。很少使用，都是在tryLock后直接判断，很少使用isLocked单独判断
         */
        boolean locked = rLock.isLocked();

        /**
         * 作为redisson对象，同样继承 RExpirable RObject中的同步 / 异步方法
         * 例如设置过期时间等等。不再赘述
         */
        return R.ok();
    }


    /**
     * 公平锁。
     * 可重入锁非公平，谁抢到算谁的。而公平所则是谁先发出请求谁先抢到
     * 等待抢锁的线程会进入一个等待队列。公平锁提供队列的存活检测，检测时间为5s
     * 如果等待队列中的某个线程宕机，公平锁会为其维持位置5秒，5秒后则将下一个线程提前
     *
     * @return
     */
    @RequestMapping("/fairlock")
    public R redissonFairLock() {
        String redissonPrefix = "demo:redisson:disLock:";
        AttrEntity attrEntity = attrDao.selectById(2L);

        /**
         * 获取一个公平锁
         * 注意，虽然这里返回的仍然是RLock，但更加具体的类其实是 RedissonFairLock
         */
        RLock fairLock = redissonClient.getFairLock(redissonPrefix + "fairlock");

        /**
         * 公平锁除了提供公平队列和存活检测，其他和RLock相同
         */

        return R.ok();
    }


    /**
     * 联锁
     * 基于Redis的Redisson分布式联锁RedissonMultiLock对象可以将多个RLock对象关联为一个联锁，
     * 每个RLock对象实例可以来自于不同的Redisson实例。
     * 联锁的使用场景?想不到哎
     * <p>
     * https://www.javadoc.io/doc/org.redisson/redisson/3.10.0/org/redisson/RedissonMultiLock.html
     *
     * @return
     */
    @RequestMapping("/multilock")
    public R redissonMultiLock() {
        String redissonPrefix = "demo:redisson:disLock:";
        AttrEntity attrEntity = attrDao.selectById(2L);

        RLock rLock1 = redissonClient.getLock(redissonPrefix + "rlock1");
        RLock rLock2 = redissonClient.getLock(redissonPrefix + "rlock2");
        RLock rLock3 = redissonClient.getLock(redissonPrefix + "rlock3");

        /**
         * 可以向其中添加任意多的redisson锁，只要是实现了redisson锁的都可以
         * 联锁的特点 : 每个RLock对象实例可以来自于不同的Redisson实例  的意思也在于此，就是说这些锁可以是由别的RedissonClient创建的
         */
        RedissonMultiLock redissonMultiLock = new RedissonMultiLock(rLock1, rLock2, rLock3);

        /**
         * lock / lockInterruptibly / tryLock
         * 上锁
         * 必须内部的三个锁全部上锁成功，才算这个联锁上锁成功
         */
        try {
            redissonMultiLock.tryLock(10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        /**
         * 解锁
         */
        // redissonMultiLock.unlock();

        /**
         * 联锁本身不是锁，因此没有过期时间这个说法。他就是个操作内部锁的对象体
         */

        return R.ok();
    }


    /**
     * 可重入读写锁。允许同时有多个读锁处于加锁状态，或者仅有一个写锁处于加锁状态
     *
     * @return
     */
    @RequestMapping("/readwritelock")
    public R redissonReadWriteLock(){
        String redissonPrefix = "demo:redisson:disLock:";

        /**
         * 获取一个读写锁
         */
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock(redissonPrefix + "readwritelock");

        /**
         * 在读写锁的基础上获取读锁和写锁。当然直接级联到上一条命令也可以
         */
        RLock readLock = readWriteLock.readLock();
        RLock writeLock = readWriteLock.writeLock();

        /**
         * 对于读锁而言，允许同一时刻存在多个读锁
         * redisson会在redis中维护一个队列，存放所有申请了读锁的线程
         * 当读锁上锁时，获取写锁将会失败
         */
        readLock.lock(1000,TimeUnit.SECONDS);

        /**
         * 同一时间只能有一个写锁
         * 当写锁上锁时，无论是读锁还是写锁都将获取失败
         */
        // writeLock.lock(1000, TimeUnit.SECONDS);

        return R.ok();
    }


    /**
     * 信号量锁。
     * @return
     */
    @RequestMapping("/semaphore")
    public R redissonSemaphore(){
        String redissonPrefix = "demo:redisson:disLock:";
        AttrEntity attrEntity = attrDao.selectById(2L);

        /**
         * 获取一个信号量
         */
        RSemaphore semaphore = redissonClient.getSemaphore(redissonPrefix + "semaphore");

        /**
         * 尝试设置初始信号量
         * 如果这个信号量锁已经存在，则该命令无效
         */
        semaphore.trySetPermits(10);

        /**
         * 尝试在指定时间内获取信号量
         */
        try {
            semaphore.tryAcquire(2, 10, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        /**
         * 尝试释放信号量
         */
        semaphore.release(2);

        /**
         * 继承RObject的相关接口，如设置过期时间 / 删除等等
         */

        return R.ok();
    }

}
